package net.da.core.manager;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;

import com.google.inject.Inject;
import com.google.inject.Singleton;

import net.da.core.bof.Acl;
import net.da.core.bof.ApplicationSession;
import net.da.core.bof.Group;
import net.da.core.bof.OrganizationUnit;
import net.da.core.bof.OrganizationUser;
import net.da.core.bof.PersistentObject;
import net.da.core.bof.BusinessObject;
import net.da.core.bof.SysUser;
import net.da.core.bof.spi.local.AclImpl;
import net.da.core.bof.spi.local.ApplicationSessionImpl;
import net.da.core.bof.spi.local.GroupImpl;
import net.da.core.bof.spi.local.OrganizationUnitImpl;
import net.da.core.bof.spi.local.OrganizationUserImpl;
import net.da.core.bof.spi.local.PersistentObjectImpl;
import net.da.core.bof.spi.local.BusinessObjectImpl;
import net.da.core.bof.spi.local.SysUserImpl;
import net.da.core.dao.Dao;
import net.da.core.dao.spi.local.AbstractDao;
import net.da.core.dao.spi.local.ApplicationSessionDaoImpl;
import net.da.core.dao.spi.local.OrganisationUserDaoImpl;
import net.da.core.entity.PersistentObjectEntity;

@Singleton
public class BusinessObjectManagerImpl implements BusinessObjectManager {

    private static final Map<Class<? extends PersistentObject>, Class<? extends PersistentObjectImpl>> ENTITIES_CLASSES_MAP = new HashMap<Class<? extends PersistentObject>, Class<? extends PersistentObjectImpl>>();

    /**
     * Contains a map of DAO interfaces to their implementations.
     */
    private static final Map<Class<? extends Dao>, Dao> DAO_CLASSES_MAP = new HashMap<Class<? extends Dao>, Dao>();

    @Inject
    private EntityManagerFactory entityManagerFactory;

    public BusinessObjectManagerImpl() {
    }

    /**
     * {@inheritDoc}
     */
    public <D extends Dao> D getDao(Class<D> daoClass) {
        D result = (D) DAO_CLASSES_MAP.get(daoClass);
        if (result == null) {
            throw new RuntimeException("No DAO specified for class [" + daoClass.getName() + "]");
        }
        // DDZ This mandatory inizialization for all the DAO classes. Earlier it can be removed to the
        // DAO class creation place.
        ((AbstractDao)result).setEmf(entityManagerFactory);
        return result;
    }

    public <E extends PersistentObject> E createBusinessObjectEmptyInstance(Class<E> cls) {
        try {
            Class businessObjectImplementationClazz = EntityClassMapInfo
                    .getBusinessObjectImplementationByInterface(cls);
            PersistentObjectImpl persistentObjectImpl = (PersistentObjectImpl) businessObjectImplementationClazz
                    .newInstance();

            Class entityClazz = persistentObjectImpl.getEntityClass();
            PersistentObjectEntity persistentObjectEntity = (PersistentObjectEntity) entityClazz.newInstance();

            persistentObjectImpl.setEntity(persistentObjectEntity);

            persistentObjectImpl.setBusinessObjectFactory(this);

            // DDZ Newly created object can't be deleted. This is part
            // of business logic and we need to think where it should be.
            persistentObjectImpl.getEntity().setDeleted(false);

            return (E) persistentObjectImpl;
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    public <E extends PersistentObject> E getBusinessObjectById(Class<E> cls, Serializable id) {
        EntityManager entityManager = null;
        try {
            Class businessObjectImplementationClazz = EntityClassMapInfo
                    .getBusinessObjectImplementationByInterface(cls);
            PersistentObjectImpl persistentObjectImpl = (PersistentObjectImpl) businessObjectImplementationClazz
                    .newInstance();

            entityManager = entityManagerFactory.createEntityManager();
            Object object = entityManager.find(persistentObjectImpl.getEntityClass(), id);
            persistentObjectImpl.setEntity((PersistentObjectEntity) object);

            persistentObjectImpl.setBusinessObjectFactory(this);
            return (E) persistentObjectImpl;
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } finally {
            entityManager.close();
        }
    }

    public <E extends PersistentObject, T extends PersistentObjectEntity> E getBusinessObjectByEntity(Class<E> cls,
            T entity) {
        try {
            Class businessObjectImplementationClazz = EntityClassMapInfo
                    .getBusinessObjectImplementationByInterface(cls);
            PersistentObjectImpl persistentObjectImpl = (PersistentObjectImpl) businessObjectImplementationClazz
                    .newInstance();

            persistentObjectImpl.setEntity(entity);

            persistentObjectImpl.setBusinessObjectFactory(this);
            return (E) persistentObjectImpl;
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    public <E extends PersistentObject, T extends PersistentObjectEntity> Collection<E> getBusinessObjectsByEntities(Class<E> cls,
            Collection<T> entities) {
        try {
            List<E> boList = new ArrayList<E>();

            for (T entity : entities) {
                Class businessObjectImplementationClazz = EntityClassMapInfo
                        .getBusinessObjectImplementationByInterface(cls);
                PersistentObjectImpl persistentObjectImpl = (PersistentObjectImpl) businessObjectImplementationClazz
                        .newInstance();
                persistentObjectImpl.setEntity(entity);
                persistentObjectImpl.setBusinessObjectFactory(this);
                boList.add((E) persistentObjectImpl);
            }
            return boList;
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Init of all persistent entities
     */
    static {
        setUpDAOs();
        setUpEntities();
    }

    private static void setUpEntities() {
        // TODO: sort entities by service
        ENTITIES_CLASSES_MAP.put(PersistentObject.class, PersistentObjectImpl.class);
        ENTITIES_CLASSES_MAP.put(Acl.class, AclImpl.class);
        ENTITIES_CLASSES_MAP.put(Group.class, GroupImpl.class);
        ENTITIES_CLASSES_MAP.put(OrganizationUser.class, OrganizationUserImpl.class);
        ENTITIES_CLASSES_MAP.put(OrganizationUnit.class, OrganizationUnitImpl.class);
        ENTITIES_CLASSES_MAP.put(BusinessObject.class, BusinessObjectImpl.class);
        ENTITIES_CLASSES_MAP.put(SysUser.class, SysUserImpl.class);
        ENTITIES_CLASSES_MAP.put(ApplicationSession.class, ApplicationSessionImpl.class);
    }

    /**
     * Initialize the map of DAO interfaces to their implementations.
     */
    private static void setUpDAOs() {
        // DDZ IMPORTANT !!!
        // See method setupping DAO for additional initialization.

        // Security service DAOs
        DAO_CLASSES_MAP.put(OrganisationUserDaoImpl.class, new OrganisationUserDaoImpl());
        DAO_CLASSES_MAP.put(ApplicationSessionDaoImpl.class, new ApplicationSessionDaoImpl());
    }

    /**
     * Helper class for get classInfo by entity class
     */
    public static class EntityClassMapInfo {
        public static <E extends PersistentObjectImpl> Class<E> getBusinessObjectImplementationByInterface(Class cls) {
            Class result = ENTITIES_CLASSES_MAP.get(cls);
            if (result == null) {
                throw new RuntimeException("No business object mapping for interface = " + cls);
            }
            return result;
        }
    }

    public <E extends PersistentObject> void remove(E businessObject) {
        EntityManager entityManager = null;
        try {
            entityManager = entityManagerFactory.createEntityManager();
            entityManager.getTransaction().begin();
            entityManager.remove(((PersistentObjectImpl)businessObject).getEntity());
            entityManager.getTransaction().commit();
        } finally {
            entityManager.close();
        }
    }

    public <E extends PersistentObject> void save(E businessObject) {
        EntityManager entityManager = null;
        try {
            entityManager = entityManagerFactory.createEntityManager();
            entityManager.getTransaction().begin();
            entityManager.persist(((PersistentObjectImpl)businessObject).getEntity());
            entityManager.getTransaction().commit();
        } finally {
            entityManager.close();
        }
    }

}
